package org.xteam.box2text;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.xteam.box2text.box.Box;
import org.xteam.box2text.box.BoxFactory;
import org.xteam.box2text.box.GOperator;
import org.xteam.box2text.box.HOperator;
import org.xteam.box2text.box.Operator;
import org.xteam.box2text.box.OperatorKinds;
import org.xteam.box2text.box.Option;
import org.xteam.box2text.box.ROperator;
import org.xteam.box2text.box.SLOperator;
import org.xteam.box2text.box.TextBox;
import org.xteam.box2text.box.util.BoxSwitch;

public class RemoveGroups extends BoxSwitch<List<Box>> {
	
	// XXX use 2 instead of 4 because we don't handle implicit separators 
	private static final int SL_SIZE = 2;
	
	private boolean splice;

	@Override
	public List<Box> caseTextBox(TextBox object) {
		return Arrays.asList(new Box[] { object });
	}
	
	public List<Box> caseOperator(Operator op) {
		List<Box> args = new ArrayList<Box>(op.getArguments());
		op.getArguments().clear();
		op.getArguments().addAll(normalizeChildren(args, true));
		return Arrays.asList(new Box[] { op });
	}
	
	@Override
	public List<Box> caseSLOperator(SLOperator object) {
		GOperator newOp = BoxFactory.eINSTANCE.createGOperator();
		Option opt;
		newOp.getOptions().add(opt = BoxFactory.eINSTANCE.createOption());
		opt.setName("gs"); opt.setValue(SL_SIZE);
		newOp.getOptions().addAll(object.getOptions());
		newOp.setOperator(object.getOperator());
		newOp.getArguments().addAll(object.getArguments());
		return doSwitch(newOp);
	}
	
	@Override
	public List<Box> caseGOperator(GOperator object) {
		List<Box> children = normalizeChildren(object.getArguments(), false);
		if (object.getOperator() == OperatorKinds.NONE) {
			if (splice)
				return children;
			// we are inside an other G operator: recreate the G box
			return Arrays.asList(new Box[] { object }); 
		}
		OperatorKinds op = object.getOperator();
		int gs = getGroupSize(object.getOptions());
		List<Box> results = new ArrayList<Box>();
		int index = 0;
		int nGroup = (children.size()+gs-1)/gs;
		for (int i = 0; i < nGroup; ++i) {
			List<Box> args = null;
			if (index + gs >= children.size()) {
				args = subList(children, index, children.size());
				// complete the group with empty H operators
				for (int j = args.size(); j < gs; ++j) {
					args.add(BoxFactory.eINSTANCE.createHOperator());
				}
			} else {
				args = subList(children, index, index+gs);
			}
			Operator newOp = makeOp(op, args);
			copyOptions(newOp.getOptions(), object.getOptions());
			results.add(newOp);
			index += gs;
		}
		if (splice) {
			List<Box> res = new ArrayList<Box>();
			boolean saveSplice = splice;
			splice = true;
			for (Box box : results) {
				res.addAll(doSwitch(box));
			}
			splice = saveSplice;
			return res;
		}
		// build a G operator without op attribute
		GOperator newOp = BoxFactory.eINSTANCE.createGOperator();
		newOp.getOptions().addAll(object.getOptions());
		newOp.getArguments().addAll(children);
		return Arrays.asList(new Box[] { newOp });
	}

	private void copyOptions(EList<Option> dst, EList<Option> src) {
		for (Option opt : src) {
			Option o = BoxFactory.eINSTANCE.createOption();
			o.setName(opt.getName());
			o.setValue(opt.getValue());
			dst.add(o);
		}
	}

	private int getGroupSize(EList<Option> eList) {
		for (Option opt : eList) {
			if (opt.getName().equals("gs"))
				return opt.getValue();
		}
		return 0;
	}

	private static List<Box> subList(List<Box> list, int start, int end) {
		List<Box> result = new ArrayList<Box>();
		for (int i = start; i < end; ++i) {
			result.add(list.get(i));
		}
		return result;
	}

	private static Operator makeOp(OperatorKinds op, List<Box> arguments) {
		if (op == OperatorKinds.R) {
			ROperator rOp = BoxFactory.eINSTANCE.createROperator();
			rOp.getArguments().addAll(arguments);
			return rOp;
		}
		if (op == OperatorKinds.H) {
			HOperator rOp = BoxFactory.eINSTANCE.createHOperator();
			rOp.getArguments().addAll(arguments);
			return rOp;
		}
		throw new RuntimeException("makeOp: not completed");
	}
	
	private List<Box> normalizeChildren(List<Box> arguments, boolean b) {
		boolean saveSplice = splice;
		splice = b;
		List<Box> results = new ArrayList<Box>();
		for (Box box : arguments) {
			results.addAll(doSwitch(box));
		}
		splice = saveSplice;
		return results;
	}
	
}
